//
// Created by shrike on 16-9-20.
//

#ifndef BANKQUEUE_MANAGER_H
#define BANKQUEUE_MANAGER_H

#include "random.h"
#include "event.h"
#include "queue.h"
#include "service.h"


#define INIT_ARRIVIED_EVENT_NUM 5

class Manager {
 public:
   Manager(int total_serve_time, int service_num)
       : _total_serve_time(total_serve_time),
         _service_num(service_num),
         _customer_stay_time(0),
         _total_served_customer_num(0) {}
   ~Manager(){}

  void simulate(int simulate_num);

  double get_avg_stay_time() {
    return _avg_stay_time;
  }

  double get_avg_customers() {
    return _avg_customers;
  }

 private:
  void run();
  void init();
  void end();
  int get_idle_service_index();
  void customer_arrived();
  void customer_departure();
  void generate_arrived_event(int& current_time);
  void generate_departure_event(int service_index, int current_time);

  int _total_serve_time;
  int _service_num;
  long long _customer_stay_time;
  long long _total_served_customer_num;
  Services*  _services;
  Queue<Customer> _customer_queue;
  Queue<Event>       _event_queue;
  Event*          _current_event;

  double _avg_customers;
  double _avg_stay_time;
  int _generate_arrived_time;
};


void Manager::init() {
  // init services
  _services = new Services[_service_num];
  for(int i = 0; i < _service_num; i++) {
    *(_services + i) = Services();
  }

  //init event queue
  _generate_arrived_time = 0;//shared
  while(_generate_arrived_time < INIT_ARRIVIED_EVENT_NUM) {
    generate_arrived_event(_generate_arrived_time);
  }
}

int Manager::get_idle_service_index() {
  for(int i = 0; i < _service_num; i++) {
    if(_services[i].is_idle())
      return i;
  }
  return -1;
}

void Manager::generate_arrived_event(int& current_time) {
  Event* event;
  int customer_per_minute = Random::uniform(RANDOM_PER_MINUTE);
  while (customer_per_minute > 0) {
    event = new Event(current_time);
    _event_queue.enqueue(event);
    --customer_per_minute;
  }
  ++current_time;
}

void Manager::generate_departure_event(int service_index, int current_time) {
  _services[service_index].serve_customer(*_customer_queue.front());
  _services[service_index].set_busy();
  _services[service_index].set_service_start_time(current_time);
  _customer_queue.dequeue();

  int duration = _services[service_index].get_customer_duration();
  Event* event = new Event(current_time + duration, EventType::DEPARTURE, service_index);
  _event_queue.enqueue(event);
}

void Manager::customer_arrived() {
  int idle_service_num = get_idle_service_index();
  int current_time = _current_event->occur_time;
  Customer* customer = new Customer(current_time);
  _customer_queue.enqueue(customer);
  _event_queue.dequeue();
    
  if (idle_service_num != -1)
    generate_departure_event(idle_service_num, current_time);
}

void Manager::customer_departure() {
  int current_time = _current_event->occur_time;
  int service_index = _current_event->service_index;

  _customer_stay_time += current_time -
          _services[service_index].get_customer_arrive_time();
  ++_total_served_customer_num;
  _services[service_index].set_idle();
  _event_queue.dequeue();

  if(_customer_queue.size() > 0) {
    service_index = get_idle_service_index();//always get one idle service
    generate_departure_event(service_index, current_time);
  }

}

void Manager::end() {
  for (int i = 0; i < _service_num; i++) {
    if (!_services[i].is_idle()) {
      int service_start_time = _services[i].get_service_start_time();
      int arrive_time = _services[i].get_customer_arrive_time();
      int duration = _services[i].get_customer_duration();

      _customer_stay_time += service_start_time + duration - arrive_time;
      ++_total_served_customer_num;
    }
  }

  _customer_queue.clear();
  _event_queue.clear();
  delete[] _services;
}

void Manager::run() {
  while (_event_queue.size() != 0) {
    _current_event = _event_queue.front();
    if (_current_event->occur_time >= _total_serve_time)
      break;
    if(_customer_queue.size() == 0 && _event_queue.size() <= _service_num)
    {
      generate_arrived_event(_generate_arrived_time);
      _current_event = _event_queue.front();//update current event, deal it with order
    }

    if (_current_event->event_type == EventType::ARRIVIED)
      customer_arrived();
    else if (_current_event->event_type == EventType::DEPARTURE)
      customer_departure();
  }

}

void Manager::simulate(int simulate_num) {
  long long total_serve_time = _total_serve_time * simulate_num;

  while(simulate_num-- > 0) {
    init();
    run();
    end();
  }

  _avg_stay_time = static_cast<double>(_customer_stay_time) / _total_served_customer_num;
  _avg_customers = static_cast<double>(_total_served_customer_num) / total_serve_time;

}

#endif //BANKQUEUE_MANAGER_H
